/*Copyright 2012 Google Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
#limitations under the License.*/


function AngleBetween(a, b) {
  var dotProd = a.dot(b);
  var lenProd = a.length() * b.length();
  var divOperation = dotProd / lenProd;
  return Math.acos(divOperation); // * (180.0 / Math.PI);
}

var gLoadoutLoader = {
  _actual : null,
  _queue : [],
  _monitored : false,
  _onLoaded : function(data) {
    var p = gLoadoutLoader._actual;
    var l = JSON.parse(data.response);
    // var l = r[p.shipLoadout.configuration]||{};
    for (var i=0;i<l.length;i++) {
      var t = {"tiers": l[i].powerLevels}
      t.tiers.push(""); // add an empty tier to indicate 'maxed out'
      p.shipLoadout.slots.push(t);
      p.shipLoadout.slots[i].tier = 0;
    }
    if (p.setupLoadout) p.setupLoadout();
    gLoadoutLoader._actual = null; // free-up the loader
  },
  _load : function(player) {
    gLoadoutLoader._actual = player;
    xhrGet("/loadout?type=bomber", false, gLoadoutLoader._onLoaded);
  },
  queueMe : function(player) {
    gLoadoutLoader._queue.push(player);
    if (!gLoadoutLoader._monitored) {
      gLoadoutLoader._monitored =true;
      checkWait ( gLoadoutLoader._free, gLoadoutLoader._next);
    }
  },
  _free : function() {return (gLoadoutLoader._actual===null)},
  _next : function() {
    gLoadoutLoader._load(gLoadoutLoader._queue.pop());
    if (gLoadoutLoader._queue.length==0) 
      gLoadoutLoader._monitored = false
    else
      checkWait(gLoadoutLoader._free, gLoadoutLoader._next);
  }
};

PlayerClass = EntityClass.extend({
  baseMoveSpeed: 52*5, //JJG: move 5 tiles/sec
  weapons: null,
  legRotation: 90.0,
  moving: false,
  physBody: null,
  faceAngleRadians: 0,
  ships: 3, //Z: change this with the number of ships the player takes on mission
  health: 100,
  maxHealth: 100,
  energy: 100,
  maxEnergy: 100,
  type: Constants.TYPE.player,
  color: 0, 
  score: 0,
  credits: 0,
  powerUpTime:0,
  invincibleTime:0,
  slot: 0, // Z: powerup slot selected
  statChange:false, // Z: used to determine if the gui needs to be updated (score, pickup etc.)
  isDead:false,
  zIndex: 1,
  collidesWith: ['enemy','eprojectile','pickup','mapobject'],
  categories:['player'],
  
  init: function (inputx, inputy, settings) {
    settings.hsize = {x:26, y:26};
    this.parent(inputx, inputy, settings);
    this.weapons = [null, null, null];
    var entityDef = {
      id: "player",
      x: this.pos.x,
      y: this.pos.y,
      halfHeight: this.hsize.x / 2, //JJG: divide by 2 to let the player squeeze through narrow corridors
      halfWidth: this.hsize.y / 2,
      damping: 0,
      angle: 0,
      categories: this.categories,
      collidesWith: this.collidesWith,
      userData: {
        "id": "player",
        "ent": this
      }
    };
    this.physBody = gPhysicsEngine.addBody(entityDef);

    // configure the selected loadout
    this.shipLoadout = {};
    this.shipLoadout.configuration = this.shipConfiguration || "standard";
    this.shipLoadout.slots = [];
    gLoadoutLoader.queueMe(this); // ask loader to get the loadout and configure for this player.

    this.drones = []; // Z: list of drones
	
    this.userID = settings.userID;
    this.displayName = settings.displayName;
    this.resetStats();

  },

  update: function () 
  {

    this.parent();

    if(this.powerUpTime>0) 
      this.powerUpTime -=0.05;//CLM attempt to make this 'seconds' accurate?
    else
    	this.powerUpTime =0;

    if (this.invincibleTime>0) {
      this.invincibleTime -=0.05;
      if (this.invincibleTime<=0) this.revertToNormal();
    } else if (this.stealthTime>0) {
      this.stealthTime -=0.05;
      if (this.invincibleTime<=0) this.revertToNormal();
    }
    
    for(var i =0; i < this.weapons.length; i++)
      if (this.weapons[i]) this.weapons[i].onUpdate(this);

    if(this.health<=0)
    {
        this.isDead = true;
        this.physBody.SetActive(false);
        this.ships -=1;
        if (this.ships>0) gGameEngine.respawnEntity({from: this.name});
    }
    else
    {
        //did we just respawn?
        if(this.isDead)
        {
            this.isDead = false;
            this.physBody.SetActive(true);
            var pPos = this.physBody.GetPosition();
            //var ent = gGameEngine.spawnEntity("SpawnEffect", pPos.x, pPos.y, null);
            //ent.onInit(this,pPos);
    	}
    }
    this.energy = Math.min(this.maxEnergy,this.energy+.2);
  },

  applyInputs: function () {
    if (this.pInput) {
      this.moving = this.pInput.moving;
      var offx = this.distanceXTo(gLevel.scrollPoint);
      var vx = this.pInput.x;
      // ensure the player stays within 400 x-distance of scrollpoint by disabling x move if out of bound
      if ((offx>gLevel.xMaxOffset && vx<0) || (offx<(-gLevel.xMaxOffset) && vx>0)) vx = 0;
      var vy = this.pInput.y; // maybe we need to bound vertically as well if level scrolls vertically
      vy += gLevel.scrollPoint.dir.y;
      vx += gLevel.scrollPoint.dir.x;
      var ovx = vx;
      var ovy = vy;
      var adjust = this.getPhysicsSyncAdjustment();
      vx += adjust.x;
      vy += adjust.y;

      this.physBody.SetLinearVelocity(new Vec2(vx, vy));
		
      //are we shooting?
      if (IS_SERVER)
      {
        for(var i =0; i < this.weapons.length; i++)
				this.weapons[i].firing = false;
			
			//CLM if you're dead, don't fire things!
			if(!this.isDead)
			{
				if(this.pInput.fire0 && 
					this.energy >= this.weapons[0].energyCost) 
				{
					this.weapons[0].onFire(this);
          this.weapons[1].onFire(this);
				} /*
				else if (this.pInput.fire1 && 
						this.energy >= this.weapons[1].energyCost) 
				{
					this.weapons[1].onFire(this);
				}
				else if (this.pInput.fire2 && 
						this.energy >= this.weapons[2].energyCost) 
				{
					this.weapons[2].onFire(this);
				}*/

        // select the highlighted weapon of the loadout
        if(this.pInput.select && this.shipLoadout.activeSlot>=0) {
          var slot = this.shipLoadout.slots[this.shipLoadout.activeSlot];
          if (!slot) return;
          var selection = slot.tiers[slot.tier];
          if (selection) slot.tier += 1;
          this.shipLoadout.activeSlot = -1;
          this.stateChange = true;
          if (selection && IS_SERVER) {
            if (selection=="speed") {
              this.moveSpeed += 52*2; // add 2 tiles / second
            } else if (selection=="missile") {
              var weapon1Class = Factory.getClass("MissileWeapon");
              this.weapons[1] = new weapon1Class();
              this.weapons[1].onInit(this);
            }
            else if (selection=="laser") {
              var weapon1Class = Factory.getClass("LaserWeapon");
              this.weapons[0] = new weapon1Class();
              this.weapons[0].onInit(this);
            }
            else if (selection=="shot") {
              var weapon1Class = Factory.getClass("StandardShot");
              this.weapons[0] = new weapon1Class();
              this.weapons[0].onInit(this);
            }
            else if (selection=="drone") {
              if (this.drones.length<3) this.addDrone();
            }
            else if (selection=="shield") {
              this.shield = true;
            }
          }
        }
			}
	  };

	  this.pInput.fire0 = false;
	  this.pInput.fire1 = false;
	  this.pInput.fire2 = false;

      /* original code, where fire_off is set in this.on_input
      if (this.pInput.fire0_off) {
        this.pInput.fire0 = false;
      }
      if (this.pInput.fire1_off) {
        this.pInput.fire1 = false;
      }
	   if (this.pInput.fire2_off) {
        this.pInput.fire2 = false;
      }*/
    }
  },

  sendUpdates: function () 
  {
    this.sendPhysicsUpdates(true);
  },
  

  // Z: on_  these are fired based on messages from server!

  on_input: function (msg) 
  {
    if (this.pInput && this.pInput.fire0 && !msg.fire0) {
      msg.fire0 = true;
      msg.fire0_off = true;
    }
    if (this.pInput && this.pInput.fire1 && !msg.fire1) {
      msg.fire1 = true;
      msg.fire1_off = true;
    }
	if (this.pInput && this.pInput.fire2 && !msg.fire2) {
      msg.fire2 = true;
      msg.fire2_off = true;
    }
    this.pInput = msg;
  },

  on_stats: function (msg) 
  {
  this.health = msg.health;
	this.energy = msg.energy;
	this.moveSpeed = msg.moveSpeed;
	this.powerUpTime = msg.powerUpTime;
	this.numKills = msg.numKills;
  },

  takeDamage: function(amount, toFixture) 
  {
    if (!this.invincibleTime) {
      if (this.shield) {
        this.shield = false;
      } else {
        this.health -= amount;
      }
    }
  },

  pickupLoot: function(lootType)
  {
    if(lootType=='health') {
      this.health = Math.max(this.health+75,this.maxHealth)
    } else if(lootType=='power') {
      this.shipLoadout.activeSlot += 1;
      if (this.shipLoadout.activeSlot > this.shipLoadout.slots.length) this.shipLoadout.activeSlot = 1;
    } else if (lootType=='life') {
      this.ships += 1
    } else if (lootType=='tech') {
      /* handle tech package */
    }
    this.stateChange = true;
  },

  turnInvincible: function ()
  {
    this.collidesWithRevert = this.collidesWith.slice(0); // copy the array
    this.categoriesRevert = this.categories.slice(0);
    this.collidesWith= ['pickup','mapobject'];
    this.categories = ['team'];
    this.masksSet = false;
    this.invincibleTime = 8;
  },

  revertToNormal: function ()
  {
    this.collidesWith = this.collidesWithRevert.slice(0);
    this.categories= this.categoriesRevert.slice(0);
    this.masksSet = false;
    this.invincibleTime = 0;
    this.stealthTime = 0;
  },

  addDrone: function ()
  {
    var newdrone = gGameEngine.spawnEntity ('Drone', this.pos.x, this.pos.y, {mode:0, master:this.name, serial: this.drones.length});
    this.drones.push(newdrone);

  },

  resetStats:function()
  {
	this.health = 100;
	this.energy = 100;
	this.isDead = false;
	this.powerUpTime = 0;
  this.invincibleTime = 5;
  this.shield = false;
  for (var i=0;i<this.drones.length;i++) this.drones[i].kill();
  this.drones = [];
  this.shipLoadout.activeSlot = -1;
  for (var i=0;i<this.shipLoadout.slots.length;i++) this.shipLoadout.slots[i].tier=0;
  this.moveSpeed = this.baseMoveSpeed;
	this.physBody.SetActive(true);
  var weapon0Class = Factory.getClass("StandardShot");
  this.weapons[0] = new weapon0Class();
  this.weapons[0].onInit(this);

  var weapon1Class = Factory.getClass("NoWeapon");
  this.weapons[1] = new weapon1Class();
  this.weapons[1].onInit(this);

  var weapon2Class = Factory.getClass("NoWeapon");
  this.weapons[2] = new weapon2Class();
  this.weapons[2].onInit(this);

  this.stateChange = true; // update gui
  },

  on_setPosition: function (msg) {
    this.centerAt(msg);
  },
  
  on_setWeapons: function (msg) {
    /*	
	//set our weapons based on the message input
	var weapon0Class = Factory.nameClassMap[msg.wep0];
    this.weapons[0] = new(weapon0Class)();
    this.weapons[0].onInit(this);

    var weapon1Class = Factory.nameClassMap[msg.wep1];
    this.weapons[1] = new(weapon1Class)();
    this.weapons[1].onInit(this);
	
	var weapon2Class = Factory.nameClassMap[msg.wep2];
    this.weapons[2] = new(weapon2Class)();
    this.weapons[2].onInit(this);
  */    
  },
  
});
Factory.nameClassMap["Player"] = PlayerClass;

var droneFormation = [{x:-80,y:-80}, {x:-80,y:+80}, {x:+80,y:-120}];

DroneClass = EntityClass.extend({

  dir: null,
  aiInterval: 20, // every ai_interval updates, the ai is run to reduce processing
  aiTick: 0,
  master: {},
  _speed : 200,
  mode: 0, // 0 = trailing, 1 = circling, 2 = fix before
  type: Constants.TYPE.team,

  init: function (inputx, inputy, settings) 
  {
    
    this.parent(inputx, inputy, settings);

    // override configureable parameters : rotAngle, dir, speed, lifetime, damage, impact, 
    if(settings)
    {
      this._master = gGameEngine.getEntityByName(settings.master);
      this._speed = this._master._speed || this._speed;
      this.mode = settings.mode || this.mode;
      this.serial = settings.serial || 1;
      this.name = "drone"+settings.serial+settings.master;
    }
    this.collidesWith = [];
    this.categories = []; //ghost

    var entityDef = {
      id: "drone",
      x: this.pos.x,
      y: this.pos.y,
      halfHeight: 2, 
      halfWidth: 2,
      damping: 0,
      angle: 0,
      categories: this.categories,
      collidesWith: this.collidesWith,
      userData: {
        "id": "drone",
        "ent": this
      }
    };
    this.physBody = gPhysicsEngine.addBody(entityDef);

    // build a queue for trailing mode 0, longer if more drones
    this.trail = new Array();
    var trailLength = this.serial * 50;
    for (var i=0;i<trailLength;i++) {
      this.trail.push({x:this.pos.x,y:this.pos.y});
    }
    // determine the mode 2 fixed formation position
    this.formation = {x:droneFormation[this.serial].x,y:droneFormation[this.serial].y};

    if(IS_CLIENT)
    {
      this.zIndex = 20;
      this._spriteAnim = new SpriteSheetAnimClass();
      this._spriteAnim.loadSheet('sotee_sprites',"./img/sotee_sprites.png");
      var sprites = getSpriteNamesSimilarTo("droneA");
      for(var i =0; i < sprites.length; i++)
        this._spriteAnim.pushFrame(sprites[i]);
    }

  },
  
  AI: function ()
  {
    this.aiTick = 0;
    var toPos = {x:this.pos.x-80, y:this.pos.y};
    this.moving = false;
    if (this.mode==0) {
      if (this._master.moving) {
        this.moving = true;
        this.trail.push({x:this._master.pos.x,y:this._master.pos.y});
        toPos = this.trail.pop();
      }
    } else if (this.mode == 2) {
      this.moving = true;
      toPos = {x: this._master.pos.x + this.formation.x, 
               y: this._master.pos.y + this.formation.y};
    };
    // move toward toPos or hover still (=scroll with Scrollpoint);
    var v;
    if (this.moving) {
      this.dir = new Vec2(toPos.x-this.pos.x, toPos.y-this.pos.y);
      this.dir.Normalize();
      v = new Vec2(this.dir.x * this._speed, this.dir.y * this._speed);
    } else {
      v = new Vec2(gLevel.scrollPoint.dir.x, gLevel.scrollPoint.dir.y);
    };
    this.physBody.SetLinearVelocity(v);

    this.parent();
  },
  
  draw: function (fractionOfNextPhysicsUpdate) { // Z: CLIENT code only called by CLIENT
    var gMap = gGameEngine.gMap;
    var ctx = gRenderEngine.context;
  
    
    //rotate based upon velocity
    var pPos = this.pos;
    if (this.physBody) pPos = this.physBody.GetPosition();
  
    var interpolatedPosition = {x:pPos.x, y:pPos.y};
  
    if(this.physBody) {
      var velocity = this.physBody.GetLinearVelocity();
      interpolatedPosition.x += (velocity.x * Constants.PHYSICS_LOOP_HZ) * fractionOfNextPhysicsUpdate;
      interpolatedPosition.y += (velocity.y * Constants.PHYSICS_LOOP_HZ) * fractionOfNextPhysicsUpdate;
    }
    this._spriteAnim.draw(interpolatedPosition.x,interpolatedPosition.y,{rotRadians:this.rotAngle});  
  },

  update: function () 
  {
      
    this.aiTick++;
    if (this.aiTick>this.aiInterval) this.AI();
      
    this.on_stats({health:this.health});     

    if(this.markForDeath)
    {
      this.isDead = true;
      if (this.physBody) this.physBody.SetActive(false);
    }
      
    this.parent();
  },

  //--------------------------------------
  kill: function () 
  {
    //remove my physics body (IF I HAVE ONE!)
    if (this.physBody) {
      gPhysicsEngine.removeBodyAsObj(this.physBody);
      this.physBody = null;
    }
    //destroy me as an ent.
    gGameEngine.removeEntity(this);
  },
  
  
  onTouch: function (otherFixture, point, impulse) 
  {
    // drones are ghosting!
    
    return true; //return false if we don't validate the collision
  },
  
  fire: function (owningPlayer) 
  { },
    
  on_stats: function(msg) 
  { },
}); // class DroneClass

Factory.nameClassMap["Drone"] = DroneClass;

